﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using VBFOOL.MediaPlayer.Core.Interface;
namespace VBFOOL.MediaPlayer.MKV
{
    public class MKVFileSource : IInputSource
    {
        public MKVFileSource(LibEBML.Common.Matroska document, TimeSpan ProcessDueTime)
        {
            this.Document = document;
            TimeCodeScale = Document.Segment.Info.TimecodeScale / 100;
            this.ProcessClusterDueTime = ProcessDueTime;
        }
        public MKVFileSource(LibEBML.Common.Matroska document)
        {
            this.Document = document;
            TimeCodeScale = Document.Segment.Info.TimecodeScale / 100;
        }
        public string Title
        {
            get
            {
                if (Document != null)
                {
                    return Document.Segment.Info.Title;
                }
                else
                {
                    return "";
                }
            }
        }

        public void ChangeAudioTrack(MediaStreamDescription desc)
        {
            var t = m_Tracks.Where(m => m.Description == desc).FirstOrDefault();
            if (t != null)
            {
                AudioTrackID = t.Track.TrackNumber;
                m_Audio.SetTrack(t);
            }
        }

        public LibEBML.Common.Matroska Document { get; protected set; }
        internal long TimeCodeScale = 10000;
        public bool BuildStreams()
        {
            m_Tracks.Clear();
            m_Subtitles.Clear();
            foreach (var entry in Document.Segment.Tracks)
            {
                switch (entry.TrackType)
                {
                    case 1:
                        var c = m_Codecs.Where(m => m.SupportCodecID(entry.CodecID)).FirstOrDefault();
                        if (c != null)
                        {
                            MKVMediaTrack msv = new MKVMediaTrack(this, entry, MediaStreamType.Video, c.CreateCodecFilter());
                            if (VideoTrackID <= 0)
                            {
                                VideoTrackID = entry.TrackNumber;
                                m_Video = new MKVMediaStream(this);
                                //m_Video.FillQueue += Stream_FillQueue;
                                m_Video.SetTrack(msv);
                            }
                            m_Tracks.Add(msv);
                        }
                        break;
                    case 2:
                        var c2 = m_Codecs.Where(m => m.SupportCodecID(entry.CodecID)).FirstOrDefault();
                        if (c2 != null)
                        {
                            MKVMediaTrack msa = new MKVMediaTrack(this, entry, MediaStreamType.Audio, c2.CreateCodecFilter());
                            m_Tracks.Add(msa);
                            if (AudioTrackID <= 0)
                            {
                                AudioTrackID = entry.TrackNumber;
                                m_Audio = new MKVMediaStream(this);
                                //m_Audio.FillQueue += Stream_FillQueue;
                                m_Audio.SetTrack(msa);
                            }
                        }
                        break;
                    case 0x11:
                        SimpleSubtitleReader sub = new SimpleSubtitleReader(this, entry);
                        m_Subtitles.Add(sub);
                        break;
                    default:
                        continue;
                    //break;
                }

            }
            SeekList = Document.Segment.Clusters.Select(m => m.ClusterPosition - Document.Segment.SeekPosition).ToList();
            CueList = Document.Segment.Cues.GroupBy(m => m.CueTime, m => m.CueTrackPositions.First()).ToDictionary(m => m.Key * TimeCodeScale, m => m.First().ClusterPosition);
            ProcessCluster();
            System.Threading.Thread th = new System.Threading.Thread(ProcessClusters);
            th.IsBackground = true;
            th.Start();
            return true;
        }

        List<SimpleSubtitleReader> m_Subtitles = new List<SimpleSubtitleReader>();
        public IEnumerable<ISubtitleReader> Subtitles { get { return m_Subtitles as IEnumerable<ISubtitleReader>; } }

        List<MKVMediaTrack> m_Tracks = new List<MKVMediaTrack>();
        public IEnumerable<IMediaTrack> Tracks
        {
            get
            {
                return m_Tracks as IEnumerable<IMediaTrack>;
            }
        }

        List<ICodecFactory> m_Codecs = new List<ICodecFactory>();

        public IList<ICodecFactory> Codecs
        {
            get { return m_Codecs; }
        }

        public bool CanSeek
        {
            get { return true; }
        }

        public decimal Duration
        {
            get { return Document.Segment.Info.Duration * TimeCodeScale; }
        }
        MKVMediaStream m_Video = null, m_Audio = null;
        int VideoTrackID = -1, AudioTrackID = -1;
        public IMediaStream AudioStream { get { return m_Audio; } }
        public IMediaStream VideoStream { get { return m_Video; } }

        void ReadCluster(int ClusterID)
        {
            var cls = Document.Segment.Clusters;
            var cluster = cls[ClusterID];
            foreach (var item in cluster.Blocks)
            {
                if (item.TrackNumber == VideoTrackID)//Video
                {
                    m_Video.Enqueue(cluster, item);
                }
                else if (item.TrackNumber == AudioTrackID)//Audio
                {
                    m_Audio.Enqueue(cluster, item);
                }
                else if (m_Subtitles.Any(m => m.Track.TrackNumber == item.TrackNumber))
                {
                    var sub = m_Subtitles.First(m => m.Track.TrackNumber == item.TrackNumber);
                    sub.Enqueue(cluster, item);
                }
            }
        }
        int CurrentClusterID = 0;

        void ProcessClusters()
        {
            var cls = Document.Segment.Clusters;
            while (CurrentClusterID < cls.Count && state >= 0)
            {
                lock (this)
                {
                    ProcessCluster();
                }
            }
            ProcessEnd();
        }

        private void ProcessEnd()
        {
            if (m_Video != null)
            {
                m_Video.Enqueue(null, null);
            }
            if (m_Audio != null)
            {
                m_Audio.Enqueue(null, null);
            }
        }

        private void ProcessCluster()
        {
            bool b1 = false, b2 = false;
            if (m_Video != null)
            {
                b1 = m_Video.FrameCount < 30;
            }
            if (m_Audio != null)
            {
                b2 = m_Audio.FrameCount < 60;
            }
            if (b1 || b2)
            {
                ReadCluster(CurrentClusterID);
                CurrentClusterID++;
            }
            else
            {
                System.Threading.Thread.Sleep(ProcessClusterDueTime);
            }
        }

        int state = 0;
        TimeSpan ProcessClusterDueTime = TimeSpan.FromMilliseconds(150);
        IList<long> SeekList = null;
        IDictionary<long, long> CueList = null;
        bool FirstSeek = true;
        public long Seek(long SeekToTime)
        {
            if (FirstSeek)
            {
                FirstSeek = false;
                return 0;
            }
            long ms = SeekToTime;
            var cue = CueList.Where(m => m.Key <= ms).LastOrDefault();
            if (cue.Value > 0)
            {
                for (int i = 0; i < SeekList.Count; i++)
                {
                    if (SeekList[i] == cue.Value)
                    {
                        lock (this)
                        {
                            CurrentClusterID = i;
                            if (m_Video != null)
                            {
                                m_Video.ClearQueue();
                            }
                            if (m_Audio != null)
                            {
                                m_Audio.ClearQueue();
                            }
                            ProcessCluster();
                        }
                        return cue.Key;
                    }
                }
            }
            return 0;
        }
        public void Close()
        {
            lock (this)
            {
                state = -1;
                if (m_Video != null)
                {
                    m_Video.ClearQueue();
                }
                if (m_Audio != null)
                {
                    m_Audio.ClearQueue();
                }
                ProcessEnd();
            }
        }
    }
}
